/**
 * 
 */
package com.sogeti.mentormatch.aop.spring;

import java.util.Hashtable;
import java.util.List;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;

import com.sogeti.mentormatch.MentorMatchException;
import com.sogeti.mentormatch.aop.AuthenticationManager;
import com.sogeti.mentormatch.aop.EmployeeGradeManager;
import com.sogeti.mentormatch.aop.UserManager;
import com.sogeti.mentormatch.data.EmployeeGrade;
import com.sogeti.mentormatch.data.User;

/**
 * @author Matthew L. Maurer <matthew.maurer@us.sogeti.com>
 */
public class LdapAuthenticationManagerImpl implements AuthenticationManager
{
    private UserManager          userManager;
    private EmployeeGradeManager employeeGradeManager;

    // Error message strings
    public static String         FAILED_LOGIN_MESSAGE          = "Incorrect username or password.";

    // other constant strings
    public static String         DEFAULT_EMPLOYEEGRADE_NAME    = "Consultant";

    // LDAP static strings
    public static String         AUTHENTICATION_DOMAIN_PREFIX  = "sogeti\\";
    public static String         ATTRIBUTE_FOR_USERNAME        = "sAMAccountName";
    public static String         LDAP_CONN_STRING              = "ldap://Sgtdc02.sogeti.com:389";
    public static String         LDAP_SEARCH_BASE              = "DC=sogeti,DC=com";
    public static String         FIRST_NAME_ATTRIBUTE_NAME     = "givenName";
    public static String         LAST_NAME_ATTRIBUTE_NAME      = "sn";
    public static String         EMPLOYEE_GRADE_ATTRIBUTE_NAME = "title";

    /*
     * (non-Javadoc)
     * 
     * @see com.sogeti.mentormatch.spring.AuthenticationManager#login(java.lang.String,
     *      java.lang.String)
     */
    @Override
    public User login ( String username, String password )
            throws AuthenticationException
    {
        List<User> foundUsers;
        User userToReturn = null;
        List<EmployeeGrade> foundEmployeeGrades;
        EmployeeGrade employeeGrade = null;
        Hashtable<String, String> env = new Hashtable<String, String>();

        env.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");
        // set security credentials, note using simple cleartext authentication
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, AUTHENTICATION_DOMAIN_PREFIX
                + username);
        env.put(Context.SECURITY_CREDENTIALS, password);

        // connect to my domain controller
        env.put(Context.PROVIDER_URL, LDAP_CONN_STRING);

        try {

            // Create the initial directory context
            // This is the failing point of the login attempt.
            LdapContext ctx = new InitialLdapContext(env, null);

            // search the db for the logged in user.
            foundUsers = this.userManager.findByUserName(username);

            // we should have found only one user, first check the size
            if ( foundUsers.size() > 0 ) {
                // being stupid here and pulling the first user from the list
                // FIXME Find a better way to handle this.
                userToReturn = foundUsers.get(0);
            }

            // we need to build the user object, they have never logged in
            // before.
            if ( userToReturn == null ) {
                // get a handle on a new user :D.
                userToReturn = new User();

                // Create the search controls
                SearchControls searchCtls = new SearchControls();

                // Specify the search scope
                searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);

                // specify the LDAP search filter
                String searchFilter = "(&(objectClass=user)("
                        + ATTRIBUTE_FOR_USERNAME + "=" + username + "))";

                // Specify the Base for the search
                String searchBase = LDAP_SEARCH_BASE;

                // Only need the surname, givenName and title to properly
                // construct
                // a User object.
                String returnedAtts[] = { FIRST_NAME_ATTRIBUTE_NAME,
                        LAST_NAME_ATTRIBUTE_NAME, EMPLOYEE_GRADE_ATTRIBUTE_NAME };
                searchCtls.setReturningAttributes(returnedAtts);

                // Search for objects using the filter
                NamingEnumeration<SearchResult> answer = ctx.search(searchBase,
                        searchFilter, searchCtls);

                // hopefully we only get one answer back, but we still need to
                // loop through results
                while ( answer.hasMoreElements() ) {
                    SearchResult sr = answer.next();

                    Attributes attrs = sr.getAttributes();

                    userToReturn.setFirstname(( String ) attrs.get(
                            FIRST_NAME_ATTRIBUTE_NAME).get());
                    userToReturn.setLastname(( String ) attrs.get(
                            LAST_NAME_ATTRIBUTE_NAME).get());

                    foundEmployeeGrades = this.employeeGradeManager
                            .findByName(( String ) attrs.get(
                                    EMPLOYEE_GRADE_ATTRIBUTE_NAME).get());

                    // we should have found only one employee grade, first check
                    // the size
                    if ( foundEmployeeGrades.size() > 0 ) {
                        employeeGrade = foundEmployeeGrades.get(0);
                    }
                    else {
                        // TODO Ensure that we find something here.
                        // FIXME Find a better way to handle this.
                        foundEmployeeGrades = this.employeeGradeManager
                                .findByName(DEFAULT_EMPLOYEEGRADE_NAME);
                        employeeGrade = foundEmployeeGrades.get(0);
                    }

                    userToReturn.setEmployeeGrade(employeeGrade);
                    
                    this.userManager.create(userToReturn);
                }
            }
        }
        // Catches a failed login rather than ANY naming problem.
        catch ( AuthenticationException authExc ) {
            throw new AuthenticationException(FAILED_LOGIN_MESSAGE);
        }
        catch ( NamingException e ) {
            // TODO Think of a better way to report this error... rethrow the
            // error?
            System.err.println("Problem searching directory: " + e);
        }

        return userToReturn;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.sogeti.mentormatch.spring.AuthenticationManager#logout(com.sogeti.mentormatch.data.User)
     */
    @Override
    public void logout ( User user ) throws MentorMatchException {
        // TODO Auto-generated method stub

    }

    /*
     * (non-Javadoc)
     * 
     * @see com.sogeti.mentormatch.spring.AuthenticationManager#setUserManager(com.sogeti.mentormatch.spring.UserManager)
     */
    @Override
    public void setUserManager ( UserManager userManagerToSet ) {
        this.userManager = userManagerToSet;
    }

    /**
     * Sets the <code>EmployeeGradeManager</code> to be used to resolve
     * consultant grade 'resolution'. Currently the database does not allow the
     * employeeGrade to be null so a grade must be constructed on first logon
     * from a name out of LDAP.
     * 
     * @param employeeGradeManagerToSet
     */
    public void setEmployeeGradeManager (
            EmployeeGradeManager employeeGradeManagerToSet )
    {
        this.employeeGradeManager = employeeGradeManagerToSet;
    }

}
